/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.elkastud.model;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * class representing Smith-Waterman alignment detection method.
 * @author Rafał Karolewski
 */

class Quad<A> {
	/**
	 * structure representing potential local alignment
	 */
   public A x,y,z,max;

   public Quad( A x, A y, A z, A max ) {
        this.x = x;
        this.y = y;
		this.z = z;
		this.max = max;
    }
}

public class SmithWatermanMatch3 {

	/** próg punktowy dla dopasowania lokalnego */
    private int score_threshold = 20;
	/** Porównywane sekwencje (wejściowe) */
    private DNASequence mA, mB, mC;
	/** Macierz oceny */
    public short mMatchArray[][][];
	/** Macierz kierunków, zawiera informację, z którego kierunku pochodzi aktualna ocena */
    public byte mDirections[][][];
	/** Implementacja macierzy przeszukanych już ścieżek lokalnych dopasowań */
    public byte paths[][][];
	/** Implementacja macierzy podobieństwa poszczególnych nukleotydów */
    private INucleobaseResemblance mResemblance;
	/**
	 * Konstruuje obiekt implementujący algorytm, inicjalizuje pamięć niezbędą do obliczeń
	 * @param a DNASequence
	 * @param b DNASequence
	 * @param c DNASequence
	 */
    public SmithWatermanMatch3(DNASequence a, DNASequence b, DNASequence c) {
        mResemblance = new NucleobaseSampleResemblance();
        this.mA = a;
        this.mB = b;
        this.mC = c;
        mMatchArray = new short[a.length() + 1][b.length() + 1][c.length() + 1];
        mDirections = new byte[a.length()][b.length()][c.length()];
		paths = new byte[a.length()][b.length()][c.length()];

        for (int i = 1; i < a.length() + 1 || i < b.length() + 1 || i < c.length() + 1; ++i) {
            if (i < a.length()) mMatchArray[i][0][0] = 0;
            if (i < b.length()) mMatchArray[0][i][0] = 0;
            if (i < c.length()) mMatchArray[0][0][i] = 0;
        }
    }
	/**
	 * Metoda implementująca algorytm Smitha-Watermana dla trzech sekwencji
	 * @return List<MatchResult3> lista rezultatów lokalnego dopasowania
	 */
    public List<MatchResult3> doCalculate(int threshold) {
        //We got 7 directions in which we can go + 8th case in Smith-Waterman alg directionValues[7] = 0
        short directionValues[] = new short[8];
        directionValues[7] = 0;
		if(threshold >= 10)
			this.score_threshold = threshold;
        byte currDirection;
        Nucleobase empty = new Nucleobase('-');

        for (int x = 0; x < mA.length() + 0; ++x) {
            for (int y = 0; y < mB.length() + 0; ++y) {
                for (int z = 0; z < mC.length() + 0; ++z) {
                    //Build directions
                    for (short i = 0; i < 7; ++i) {
                        //Calculate resemblance
                        Nucleobase a = empty, b = empty, c = empty;
                        if (getX(i) == -1) a = mA.getAt(x);
                        if (getY(i) == -1) b = mB.getAt(y);
                        if (getZ(i) == -1) c = mC.getAt(z);

                        directionValues[i] = (short) (mResemblance.getResemblance(a, b, c)
                                + mMatchArray[x + 1 + getX(i)][y + 1 + getY(i)][z + 1 + getZ(i)]);
                    }
                    currDirection = getMax(directionValues);
                    mDirections[x][y][z] = currDirection;
                    mMatchArray[x + 1][y + 1][z + 1] = directionValues[currDirection];
                }
            }
        }
		/** wynajdowanie dopasowań z punktacją większą niż ogreślony próg */
		// find local alignments with score greater than some fixed threshold
		List<Quad<Integer>> localAlignList = new ArrayList<Quad<Integer>>();
		int max = Integer.MIN_VALUE;
		int mX=0,mY=0,mZ=0;
		for (int x = 0; x < mA.length(); ++x) {
			for (int y = 0; y < mB.length(); ++y) {
				for (int z = 0; z < mC.length(); ++z) {
					if(this.mMatchArray[x+1][y+1][z+1] > max)
					{
						max = this.mMatchArray[x+1][y+1][z+1];
						mX = x+1;
						mY = y+1;
						mZ = z+1;
						if(max >= this.score_threshold)
						{
							localAlignList.add(new Quad<Integer>(mX, mY, mZ, max));
						}
					}
				}
			}
		}
		/** sortowanie wyników rozpoczynając od najlepszego dopasowania */
		Collections.sort(localAlignList, new Comparator(){
			public int compare(Object o1, Object o2) {
				return ((Quad<Integer>) o1).max > ((Quad<Integer>)o2).max ? -1
				 : ((Quad<Integer>) o2).max < ((Quad<Integer>)o1).max ? 1
				 : 0;
			}
		});

		List<MatchResult3> resultList = new ArrayList<MatchResult3>();
		Iterator it = localAlignList.iterator();
		int localPathsCount = 1;
		while(it.hasNext())
		{
			//Build result
			StringBuilder matchX = new StringBuilder();
			StringBuilder matchY = new StringBuilder();
			StringBuilder matchZ = new StringBuilder();

			Quad<Integer> tmp = (Quad<Integer>)it.next();
			int x = tmp.x,
				y = tmp.y,
				z = tmp.z;
			int pathLength = 0;
			if(paths[x-1][y-1][z-1] == 0)
			{
				short direction = mDirections[x-1][y-1][z-1];
				while(direction != 7 && (x>0 && y>0 && z>0))
				{
					paths[x-1][y-1][z-1] = (byte)localPathsCount;		// oznacza sciezki
					if(getX(direction)==0) matchX.append('-');
					else matchX.append(mA.getAt(x-1).getNucleobaseCode());

					if(getY(direction)==0) matchY.append('-');
					else matchY.append(mB.getAt(y-1).getNucleobaseCode());

					if(getZ(direction)==0) matchZ.append('-');
					else matchZ.append(mC.getAt(z-1).getNucleobaseCode());
					direction = mDirections[x-1][y-1][z-1];
					if(direction != 7)
					{
						x += getX(direction);
						y += getY(direction);
						z += getZ(direction);
					}
				}
				localPathsCount++;
				resultList.add( new MatchResult3(
						matchX.reverse().toString(),
						matchY.reverse().toString(),
						matchZ.reverse().toString(),
						tmp.max)
				);
			}
			else
			{
				it.remove();
//				System.out.println(" wykryto fragment poprzedniej sciezki");
			}
		}
		return resultList;
    }
	/**
	 * Sprawdź, czy w podanym kierunku następuje zmiana na trzeciej sekwenji
	 * @param d kierunek
	 * @return	0	nie nastoąpiła zmiana na trzeciej sekwencji
	 *			1	Nastąpiła zmiana na trzeciej sekwencji
	 */
    private byte getX(short d) {
        if (d == 0 || d == 1 || d == 2) {
            return 0;
        } else {
            return -1;
        }
    }
	/**
	 * Sprawdź, czy w podanym kierunku następuje zmiana na trzeciej sekwenji
	 * @param d kierunek
	 * @return	0	nie nastoąpiła zmiana na trzeciej sekwencji
	 *			1	Nastąpiła zmiana na trzeciej sekwencji
	 */
    private byte getY(short d) {
        if (d == 2 || d == 5 || d == 6) {
            return 0;
        } else {
            return -1;
        }
    }
	/**
	 * Sprawdź, czy w podanym kierunku następuje zmiana na trzeciej sekwenji
	 * @param d kierunek
	 * @return	0	nie nastoąpiła zmiana na trzeciej sekwencji
	 *			1	Nastąpiła zmiana na trzeciej sekwencji
	 */
    private byte getZ(short d) {
        if (d == 0 || d == 3 || d == 6) {
            return 0;
        } else {
            return -1;
        }
    }
	/**
	 * Zwraca indeks elementu o najwyższej wartości
	 * @param elements Wyliczone oceny dla siedmiu kierunków
	 * @return index elementu o najwyższej wartości
	 */
    private byte getMax(short elements[]) {
        short maxElem = Short.MIN_VALUE;
        byte maxIndex = 0;
        for (byte i = 0; i < elements.length; ++i) {
            if (maxElem < elements[i]) {
                maxElem = elements[i];
                maxIndex = i;
            }
        }
        return maxIndex;
    }
}
